package org.higginsexamples.multiusermail.hbxclient;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Properties;

import org.eclipse.higgins.rp.icard.token.identity.DigitalIdentity;
import org.eclipse.higgins.sts.api.IElement;
import org.eclipse.higgins.sts.common.Constants;
import org.eclipse.higgins.sts.common.Element;
import org.eclipse.higgins.sts.spi.IXMLSecurityExtension;
import org.eclipse.higgins.sts.xmlsecurity.apache.XMLSecurityApacheExtension;

public class HBXClient {

	// attributes
	private final String keyAlias;
	private final String keyPass;

	private KeyStore keystore;

	// constructor

	public HBXClient(String _keystoreFilename, String _keyAlias, String _keyPass) {
		this.keyAlias = _keyAlias;
		this.keyPass = _keyPass;

		this.keystore = getKeyStore(_keystoreFilename);
	}

	// public methods

	public DigitalIdentity callSelectorAndGetToken(String host, int port,
			Properties props) throws Exception {
		String message = createHBXRequest(props);
		System.out.println("HBX-Request:\n" + message);
		String token = sendHBXMessage(host, port, message);
		System.out.println("HBX-Response:\n" + token);

		DigitalIdentity di = decryptToken(token);
		System.out.println(di);
		return di;
	}

	private KeyStore getKeyStore(String filename) {
		KeyStore ks;
		try {
			ks = KeyStore.getInstance("JKS");
		} catch (KeyStoreException e) {
			e.printStackTrace();
			return null;
		}
		FileInputStream fis;
		try {
			System.out.println("Using Keystore: " + filename);
			fis = new FileInputStream(new File(filename));
			ks.load(fis, this.keyPass.toCharArray());
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return ks;
	}

	@SuppressWarnings("unchecked")
	private DigitalIdentity decryptToken(String strToken) throws Exception {
		try {
			IElement elemToken = new Element();
			elemToken.set(strToken);
			IXMLSecurityExtension secext = new XMLSecurityApacheExtension();
			IElement ie = null;
			// for Higgins 1-0-0
			secext.configure(new HashMap(), null, new HashMap());
			// for Higgins 1.1M5
			// secext.configure(new HashMap(), null, new HashMap(), null, null);

			PrivateKey key = (PrivateKey) keystore.getKey(keyAlias, keyPass
					.toCharArray());
			System.out.println("Decrypt token using key:\n" + key);
			System.out.println("Decrypt token using key algorithm:\n"
					+ key.getAlgorithm());
			ie = secext.DecryptElement(elemToken, key);
			if (ie == null) {
				System.err.println("Could not decrypt token");
				return null;
			} else {
				System.out.println("Decrypted token looks like\n"
						+ ie.getAs(java.lang.String.class));
				// verify that it's signed correctly
				secext.VerifyEnveloped(ie, new Constants());
				DigitalIdentity di = DigitalIdentity.fromXml(ie);
				return di;
			}
		} catch (Exception e) {
			Exception le = new Exception("can not decrypt the token");
			le.initCause(e);
			throw le;
		}
	}

	private String sendHBXMessage(String host, int port, String message)
			throws Exception {
		try {
			Socket socket = new Socket(host, port);
			PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
			BufferedReader in = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));
			out.print(message);
			out.print("\0");
			out.flush();

			StringBuffer sb = new StringBuffer();
			while (true) {
				int ch = in.read();
				if (ch == -1 || ch == '\0' || ch == 0) {
					break;
				} else {
					sb.append((char) (ch & 0xFFFF));
				}
			}
			return sb.toString();
		} catch (UnknownHostException e) {
			Exception le = new Exception("Unknown host name");
			le.initCause(e);
			throw le;
		} catch (IOException e) {
			Exception le = new Exception("IOException occuured");
			le.initCause(e);
			throw le;
		}
	}

	private String createHBXRequest(Properties props) throws Exception {
		// TODO move constants to AuthProperties
		String url = props.getProperty("SERVER");
		String tokenType = props.getProperty("tokenType");
		String requiredClaims = props.getProperty("requiredClaims");
		String optionalClaims = props.getProperty("optionalClaims");
		String issuer = props.getProperty("issuer");
		String issuerPolicy = props.getProperty("issuerPolicy");
		String privacyUrl = props.getProperty("privacyUrl");
		String privacyVersion = props.getProperty("privacyVersion");

		String cert = null;

		try {
			X509Certificate x509cert = (X509Certificate) this.keystore
					.getCertificate(this.keyAlias);
			byte[] certBytes = x509cert.getEncoded();
			cert = org.apache.xml.security.utils.Base64.encode(certBytes);
		} catch (Exception e) {
			Exception le = new Exception("Can't get certificate for alias "
					+ this.keyAlias + " from keystore" + this.keystore + ".");
			le.initCause(e);
			throw le;
		}

		StringBuffer sb = new StringBuffer();
		sb.append("<hbx_request>");
		if (url != null) {
			sb.append("<document_URL>").append(url).append("</document_URL>");
		}
		sb.append("<parameters>");
		// TokenType must start with a Upper Case "T"
		if (tokenType != null && tokenType.length() > 0)
			sb.append("<parameter name=\"TokenType\" value=\"" + tokenType
					+ "\"/>");
		if (requiredClaims != null && requiredClaims.length() > 0)
			sb.append("<parameter name=\"requiredClaims\" value=\""
					+ requiredClaims + "\"/>");
		if (optionalClaims != null && optionalClaims.length() > 0)
			sb.append("<parameter name=\"optionalClaims\" value=\""
					+ optionalClaims + "\"/>");
		if (issuer != null && issuer.length() > 0)
			sb.append("<parameter name=\"issuer\" value=\"" + issuer + "\"/>");
		if (issuerPolicy != null && issuerPolicy.length() > 0)
			sb.append("<parameter name=\"issuerPolicy\" value=\""
					+ issuerPolicy + "\"/>");
		if (privacyUrl != null && privacyUrl.length() > 0)
			sb.append("<parameter name=\"privacyUrl\" value=\"" + privacyUrl
					+ "\"/>");
		if (privacyVersion != null && privacyVersion.length() > 0)
			sb.append("<parameter name=\"privacyVersion\" value=\""
					+ privacyVersion + "\"/>");
		sb.append("</parameters>");
		sb.append("<certificate_chain>");
		sb.append("<certificate>");
		sb.append(cert);
		sb.append("</certificate>");
		sb.append("</certificate_chain>");
		sb.append("</hbx_request>");

		return sb.toString();
	}
}
